/*
 * main.cpp
 *
 *  Created on: 19.12.2008
 *      Author: felix
 */
#ifdef WIN32
#include <windows.h>
#endif

#ifdef __APPLE__
#include <OpenGL/gl.h>  // The GL Header File
#include <GLUT/glut.h>  // The GL Utility Toolkit (Glut) Header
#else
#include <GL/gl.h>      // The GL Header File
#include <GL/glut.h>    // The GL Utility Toolkit (Glut) Header
#endif

#include "cuboid.h"
#include "cube.h"
#include "constants.h"

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

struct Point {
	float x,y,z;
};

const int initialWindowWidth = 512;
const int initialWindowHeight = 512;
int width_ = 512;
int height_ = 512;

GLUquadric *quadric;

// FRUSTUM
int fovy_ = 45;
int near_ = 1;
int far_ = 1000;

Point focus;
//float top_ = 1000;
//float bottom_ = 1000;
//float left_ = 1000;
//float right_ = 1000;

double camPos[3] = {0.0, -50.0, -75.0};
GLfloat lightPos[4] = {100, -200.0, -300, 0};
int carpetSize[2] = {200, 150}; // the floor/ground

// TRANSFORMATION PARAMETERS
float rotationFactorX = 0;
float rotationFactorY = 0;
float currentZoomLevel = 1; // the level to zoom

// STATE OF CURRENT PREVIEW CUBOID
Color previewC;
Cuboid *preview;

// FLAGS
bool isAnaglyph = false;
bool firstclick = true;
bool middleButtonDown = false;

// TMP VARS
int lastX;
int lastY;
float deltaXsum=0;
float deltaYsum=0;
bool isCarpetDrawn = false;


int mousePosx, mousePosy; //global mouse pos


std::vector<Cuboid> cuboids;

const int carpetTexWidth = 512;
const int carpetTexHeight = 512;
GLuint carpetTextureIndx;
GLubyte *carpetTexture;
//char *texturePath  = "pics/spielteppich.ppm";
string texturePath = "pics/spielteppich-512.ppm";

bool screenToWorldCoordinates(int x, int y, double &wx, double &wy, double &wz) {
	if(x<0 || x>=width_ || y<0 || y>=height_)
		return false;

	double M[16];
	double P[16];
	float z;
	int V[4];

	//read depth buffer and proj / model-matrices
	glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);
	glGetDoublev(GL_MODELVIEW_MATRIX, M);
	glGetDoublev(GL_PROJECTION_MATRIX, P);
	glGetIntegerv(GL_VIEWPORT, V);
	double tmpZ = (double)z;
	gluUnProject(x, y, tmpZ, M, P, V,&wx, &wy, &wz);

	return z < 1.0;
}


bool loadCarpetTexture() {
	//load ppm
	glEnable(GL_TEXTURE_2D);
	carpetTexture = new GLubyte[carpetTexWidth*carpetTexHeight*3];

	const char *path = texturePath.c_str();

	FILE *pFile = fopen(path, "r");
	if (pFile==NULL) {
		cout << "Datei "<< path <<" nicht gefunden!" << endl;
		glDisable(GL_TEXTURE_2D);
		return false;
	}
	fread(carpetTexture, sizeof(GLubyte), carpetTexWidth*carpetTexHeight*3, pFile);

	glGenTextures(1, &carpetTextureIndx);
	glBindTexture(GL_TEXTURE_2D, carpetTextureIndx);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);	// Linear Filtering
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);	// Linear Filtering

	glTexImage2D(GL_TEXTURE_2D, 0, 3, carpetTexWidth, carpetTexHeight, 0, GL_RGB,
			GL_UNSIGNED_BYTE, carpetTexture);

	delete[] carpetTexture;
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_TEXTURE_2D);
	return true;
}

void drawCarpet() {
	glEnable(GL_TEXTURE_2D);
	glPushMatrix();
	glColor3f(1, 1, 1);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	glBindTexture(GL_TEXTURE_2D, carpetTextureIndx);

	glBegin(GL_QUADS);
		glTexCoord2f(0,0);
		glVertex3f(carpetSize[0]/2, 10, carpetSize[1]/2);
		glTexCoord2f(0,1);
		glVertex3f(carpetSize[0]/2, 10, -carpetSize[1]/2);
		glTexCoord2f(1,1);
		glVertex3f(-carpetSize[0]/2, 10, -carpetSize[1]/2);
		glTexCoord2f(1,0);
		glVertex3f(-carpetSize[0]/2, 10, carpetSize[1]/2);
	glEnd();
	glPopMatrix();
	glDisable(GL_TEXTURE_2D);
}

void drawGeometry() {

	glClear(GL_DEPTH_BUFFER_BIT); // for mergin colors (anaglyph)

	glPushMatrix();

	drawCarpet();

	for (unsigned int i=0; i < cuboids.size(); i++) {
		cuboids.at(i).draw(quadric);
	}

	double wx, wy, wz;
	if (screenToWorldCoordinates(mousePosx, height_ - mousePosy, wx, wy, wz)) {
		(*preview).x = (int)wx;
		(*preview).y = (int)wy;
		(*preview).z = (int)wz;
	}
	(*preview).draw(quadric);

	glPopMatrix();

}

//set the frustum for each rendering process in the  stereomode and normal mode
void setFrustum(bool isLeftEye) {

	double b,t;
	t = near_ * tan(fovy_ * M_PI / 360.0);
	b = -t;

	//focallength kann so berechnet werden, weil unser focus immer im nullpunkt liegt
//	double c = sqrt(position.x * position.x + position.z * position.z);
//	double focalLength = sqrt(c*c + position.y*position.y);
	double c = sqrt(camPos[0] * camPos[0] + camPos[2] * camPos[2]);
	double focalLength = sqrt(c * c + camPos[1] * camPos[1]);
	double ndfl = near_ / focalLength; //zielweite

	double aspect = width_/(float)height_;
	double widthHalf = near_ * tan(fovy_/2.0);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	//off-axis
	float left, right, top, bottom;
	if (!isLeftEye) {
		left = -aspect*widthHalf - 0.5f * EYE_SEPERATION * ndfl;
		right = aspect*widthHalf - 0.5f * EYE_SEPERATION * ndfl;
	} else {
		left = -aspect*widthHalf + 0.5f * EYE_SEPERATION * ndfl;
		right = aspect*widthHalf + 0.5f * EYE_SEPERATION * ndfl;
	}
	top = widthHalf;
	bottom = - widthHalf;
	//cout << "left: " << left << "  right: " << right << endl;

	glFrustum(left, right, top, bottom, near_, far_);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	//gluLookAt(position.x + vs.x, position.y + vs.y, position.z + vs.z,
//			focus.x + vs.x, focus.y +vs.y, focus.z + vs.z,
//			0, 1, 0);
 	gluLookAt(camPos[0], camPos[1], camPos[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void setRedAnaglyph() {
	//right eye red
	glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);

	setFrustum(false);
	drawGeometry();
}

void setBlueAnaglyph() {
	//left eye blue
	glColorMask(GL_FALSE, GL_FALSE, GL_TRUE , GL_TRUE);

	setFrustum(true);
	drawGeometry();
}


void display() {

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//rotate if middlebutton is pressed and reset the light
	if (middleButtonDown) {
		//glRotatef(rotationFactorX, 1, 0, 0);
		glRotatef(rotationFactorY, 0, 1, 0);
		glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

	}

	if (isAnaglyph) {
		//display in red-blue overlay
		setBlueAnaglyph();
		setRedAnaglyph();
		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

	} else {
		//display in normal mode
		drawGeometry();
	}
	isCarpetDrawn = false;
	glutSwapBuffers();
}

void setPerspective(int width, int height);
void reshape(int width, int height) {
	width_ = width;
	height_ = height;
//	glViewport(0,0,width_,height_);
	setPerspective(width_, height_);
}

void setPerspective(int width, int height) {

	width_ = width;
	height_ = height;

	glViewport(0, 0, width_, height_);
	setFrustum(true);


//	width_ = width;
//	height_ = height;
//
//	glViewport(0, 0, width_, height_);
//	glMatrixMode(GL_PROJECTION);
//	glLoadIdentity();
//	gluPerspective(fovy_, width_/float(height_), near_, far_);
////	glFrustum(left_, right_, bottom_, top_, near_, far_);

//	glMatrixMode(GL_MODELVIEW);
//	glLoadIdentity();
//	gluLookAt(camPos[0], camPos[1], camPos[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void mouse(int button, int state, int x, int y) {

	switch(button) {
	case GLUT_LEFT_BUTTON:
		glutPostRedisplay();
		if (state == GLUT_DOWN) {
			Cuboid c = *preview;
			cuboids.push_back(c);
			glutPostRedisplay();
		}
		break;
	case GLUT_MIDDLE_BUTTON:
		if (state == GLUT_DOWN) {
			if (firstclick) { // store location for mouse dragging (see mouseMoved)
				firstclick = false;
				middleButtonDown = true;
				lastX = x;
				lastY = height_ - y;
			}
		}
		if (state == GLUT_UP) { // set back dragging state after navigation
			firstclick = true;
			middleButtonDown = false;
			//float deltaX = -(firstClickX - x)/(float)width_;
			//float deltaY = (firstClickY - height_ + y)/(float)height_;
			//rotationFactorX = (deltaY/* + deltaYsum*/) * 180;
			//rotationFactorY = (deltaX/* + deltaXsum*/) * 180;
			/*glRotatef(rotationFactorX, 1, 0, 0);
			glRotatef(rotationFactorY, 0, 1, 0);
			rotationFactorX = 0;
			rotationFactorY = 0;
			deltaXsum += deltaX;
			deltaYsum += deltaY;*/

		}
		break;
	case GLUT_RIGHT_BUTTON:
		// reserved for context menu
		break;
	case GLUT_WHEEL_UP:
		if (currentZoomLevel > -1) {
			currentZoomLevel -= 0.05;
			glEnable(GL_NORMALIZE);
			glEnable(GL_RESCALE_NORMAL);
			glScalef(1.05,1.05,1.05);
			glDisable(GL_NORMALIZE);
			glutPostRedisplay();
		}
		break;
	case GLUT_WHEEL_DOWN:
		if (currentZoomLevel < 4) {
			currentZoomLevel += 0.05;
			glEnable(GL_NORMALIZE);
			glEnable(GL_RESCALE_NORMAL);
			glScalef(0.95,0.95,0.95);
			glDisable(GL_NORMALIZE);
			glutPostRedisplay();
		}
		break;
	}
}

void mouseMoved(int x, int y) {

	mousePosx = x;
	mousePosy = y;

	if (middleButtonDown) {

		// calc rotation parameters
		float deltaX = -(lastX - x) / (float)width_;
		float deltaY = (lastY - height_ + y) / (float)height_;
		rotationFactorX = (deltaY) * 180;
		rotationFactorY = (deltaX) * 180;

		lastX = x;
		lastY = y;

		//glRotatef(rotationFactorX, 1, 0, 0);
		//glRotatef(rotationFactorY, 0, 1, 0);
		//glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

// FIXME nicht jedesmal aufrufen
	//	if (!isAnaglyph) setPerspective(width_, height_);

		glutPostRedisplay();

	}
}

/* reads screen coordinates of mouse pointer and calculates their
 * world coordinates for preview brick */
void mouseMovedPassive(int x, int y) {

	mousePosx = x;
	mousePosy = y;

	glutPostRedisplay();
}

void keyPressed(unsigned char key, int x, int y) {
}

/* set size from context menu */
void selectSize(int value) {
	switch (value) {
	case FORM_1:
		preview->setSize(4,4,4);
		break;
	case FORM_2:
		preview->setSize(6,12,6);
		break;
	case FORM_3:
		preview->setSize(12,6,6);
		break;
	case FORM_4:
		preview->setSize(6,6,12);
		break;
	}
	glutPostRedisplay();
}

/* select color from context menu */
void selectColor(int value) {
//	cout << "menu select size. color: " << value << endl;
	switch (value) {
	case RED:
		preview->color.r = 1.0;
		preview->color.g = 0.0;
		preview->color.b = 0.0;
		break;
	case GREEN:
		preview->color.r = 0.0;
		preview->color.g = 1.0;
		preview->color.b = 0.0;
		break;
	case BLUE:
		preview->color.r = 0.0;
		preview->color.g = 0.0;
		preview->color.b = 1.0;
		break;
	case WHITE:
		preview->color.r = 1.0;
		preview->color.g = 1.0;
		preview->color.b = 1.0;
		break;
	}
	glutPostRedisplay();
}

/*select settings from menu*/
void selectSettings(int val) {
	switch (val){
	case SETTINGS_1:
		glutFullScreen();
		glutFullScreen();
		glutPositionWindow(0, 0);
//		glutGameModeString("1024x600:32");
//		glutEnterGameMode();
//		reshape(1024, 600);
		glutPostRedisplay();
		break;
	case SETTINGS_2:
//		glutLeaveGameMode();
		glutReshapeWindow(initialWindowWidth, initialWindowHeight);
		width_ = initialWindowWidth;
		height_ = initialWindowHeight;
		glutPositionWindow(50, 50);
		glutPostRedisplay();
		break;
	case SETTINGS_3:
		isAnaglyph = false;
		setPerspective(width_, height_);
		glutPostRedisplay();
		break;
	case SETTINGS_4:
		isAnaglyph = true;
		glutPostRedisplay();
		break;
	}
}

void rightMenu(int value) {
	// only submenus: no entries => no values
}

void loadContextMenu() {
	int submenu1, submenu2, submenu3;

	submenu1 = glutCreateMenu(selectSize);
	glutAddMenuEntry("4x4x4", FORM_1);
	glutAddMenuEntry("12x6x6", FORM_2);
	glutAddMenuEntry("6x12x6", FORM_3);
	glutAddMenuEntry("6x6x12", FORM_4);

	submenu2 = glutCreateMenu(selectColor);
	glutAddMenuEntry("Green", GREEN);
	glutAddMenuEntry("Red", RED);
	glutAddMenuEntry("White", WHITE);

	submenu3 = glutCreateMenu(selectSettings);
	glutAddMenuEntry("Fullscreenmode", SETTINGS_1);
	glutAddMenuEntry("Windowmode", SETTINGS_2);
	glutAddMenuEntry("Normal Displaymode", SETTINGS_3);
	glutAddMenuEntry("Anaglyph Displaymode", SETTINGS_4);


	glutCreateMenu(rightMenu);

	glutAddSubMenu("Size", submenu1);
	glutAddSubMenu("Color", submenu2);
	glutAddSubMenu("Settings", submenu3);

	glutAttachMenu(GLUT_RIGHT_BUTTON);
}

void initGL() {
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	glShadeModel(GL_SMOOTH);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	quadric = gluNewQuadric();

	loadCarpetTexture();

	loadContextMenu();

	// set color and create preview cuboid
	previewC.r =1; previewC.g =0; previewC.b =0;
	preview = new Cuboid(4,4,4,previewC, isAnaglyph);

	focus.x = 0;
	focus.y = 0;
	focus.z = 0;

	setPerspective(width_, height_);
	glEnable(GL_LIGHTING);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	glEnable(GL_LIGHT0);
	glutPostRedisplay();
}

int main(int argc, char** argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
	glutInitWindowSize(width_, height_);
	glutCreateWindow("Computergrafik I Projekt");
	initGL();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyPressed);

	/* MOUSE CALLBACKS*/
	glutMouseFunc(mouse); // buttons and mousewheel
	glutMotionFunc(mouseMoved); // dragging
	glutPassiveMotionFunc(mouseMovedPassive); // moving without click
	glutMainLoop();
}
